/*
 * gstengine.cpp - Gstreamer sound engine
 *
 * Copyright (C) 2010-2011 Serge Begger
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "gstengine.h"
#include "gstcallback.h"
#include "QDebug"

gstengine::gstengine(QObject *parent) :
    QObject(parent)
{
    settings = new QSettings("carpc", "general", this);
    settings->setPath(QSettings::IniFormat,QSettings::UserScope,"carpc");

    inputDevName = settings->value("indevpath").toString();
    if (inputDevName.isEmpty())
        inputDevName = "default";

    outputDevName = settings->value("outdevpath").toString();
    if (outputDevName.isEmpty())
        outputDevName = "default";

    tvDevName = settings->value("tvdevpath").toString();

    gst_init (0, 0);
    pipeline = 0;
    playbin = 0;
    thisVideo = false;
    loopPipeline = 0;
    initEngine = false;
    error = false;
    currVolume = 0;
    textoverlay = 0;
    widgetVideo = 0;
    fullScreen = false;
}

gstengine::~gstengine()
{
    if (loopPipeline) {
        gst_element_set_state (loopPipeline, GST_STATE_NULL);
        gst_object_unref (GST_OBJECT (loopPipeline));
    }
    if (pipeline) {
        gst_element_set_state (pipeline, GST_STATE_NULL);
        gst_object_unref (GST_OBJECT (pipeline));
    }
    if (playbin) {
        gst_element_set_state (playbin, GST_STATE_NULL);
        gst_object_unref (GST_OBJECT (playbin));
    }
}

bool gstengine::createAudioSink()
{
    audioSink = gst_element_factory_make ("alsasink", "sink");
    if (audioSink == NULL)
        return false;
    g_object_set (G_OBJECT (audioSink), "device", (const char*)outputDevName.toLatin1() , NULL);
    return true;
}

bool gstengine::createVideoSink()
{
    if (widgetVideo != 0) {
        widgetVideo->~videoWidget();
        widgetVideo = 0;
    }
    videoSink = gst_element_factory_make ("xvimagesink", "xvideoout");
    if (videoSink == NULL)
        return false;

    textoverlay = gst_element_factory_make ("textoverlay", "text");
    if (textoverlay == NULL)
        return false;

    g_object_set(G_OBJECT(textoverlay),"halign", "left","valign", "top",NULL);
    if (GST_VERSION_MAJOR < 32)
        g_object_set(G_OBJECT(textoverlay),"color", 4000180000 ,NULL);
    g_object_set(G_OBJECT(textoverlay),"font-desc", "SF Digital Readout, Bold 40",NULL);

    xinfo = new QX11Info();
    display = xinfo->display();
    g_object_set(G_OBJECT (videoSink), "display", display, NULL);
    g_object_set(G_OBJECT (videoSink), "force-aspect-ratio", true, NULL);

    widgetVideo = new videoWidget(videoFrame);
    connect(widgetVideo,SIGNAL(clickVideoWidget()), this, SLOT(clickVideoWidget()));
    return true;
}

bool gstengine::createVolumeCtrl()
{
    volumeCtrl = gst_element_factory_make ("volume", "volume");
    if (volumeCtrl == NULL)
        return false;
    g_object_set ( G_OBJECT (volumeCtrl), "volume", (double)currVolume / 100, NULL);
    return true;
}

void gstengine::initVideo() {
    if (fullScreen) {
        currGeometry = widgetVideo->geometry();
        widgetVideo->setParent(0);
        widgetVideo->showFullScreen();
    } else
        widgetVideo->show();

    win = widgetVideo->winId();
    QApplication::syncX();
    gst_x_overlay_set_xwindow_id (GST_X_OVERLAY (videoSink), win);
    gst_x_overlay_expose (GST_X_OVERLAY (videoSink));
}

void gstengine::clickVideoWidget()
{
    widgetVideo->hide();
    if (fullScreen) {
        widgetVideo->setParent(videoFrame);
        widgetVideo->setGeometry(currGeometry);
        fullScreen = false;
    } else {
        currGeometry = widgetVideo->geometry();
        widgetVideo->setParent(0);
        widgetVideo->showFullScreen();
        fullScreen = true;
        emit toFullScreen();
    }
    initVideo();
}

void gstengine::setGeometryWV(QRect geometry)
{
    if (widgetVideo)
        widgetVideo->setGeometry(geometry);
}

void gstengine::setVideoLCDtext(QString info) {
    if (textoverlay) {
        g_object_set(G_OBJECT (textoverlay), "text", info.toAscii().data() , NULL);
        QTimer::singleShot(5000, this, SLOT(clearVideoLCDtext()));
    }
}

void gstengine::clearVideoLCDtext(){
    setVideoLCDtext("");
}

bool gstengine::loopaudio()
{
    GstElement *preamp;
    loopPipeline = gst_pipeline_new ("loopback");
    gst_pipeline_set_delay(GST_PIPELINE(loopPipeline),GST_NSECOND);
    input = gst_element_factory_make ("alsasrc", "source");
    if (input == NULL)
        return false;

    if (!createVolumeCtrl())
        return false;

    g_object_set (G_OBJECT (input), "device", (const char*)inputDevName.toLatin1() , NULL);

    if (!createAudioSink())
        return false;

    equalizer = gst_element_factory_make ("equalizer-10bands", "equalizer");

    preamp = gst_element_factory_make ("volume", "preamp");

    gst_bin_add_many (GST_BIN (loopPipeline), input, volumeCtrl, audioSink, equalizer, preamp, NULL);

    gst_element_link_many (input, preamp, equalizer, volumeCtrl, audioSink, NULL);

    gst_element_set_state (GST_ELEMENT(loopPipeline), GST_STATE_PLAYING);

    initEngine = true;

    return true;
}

bool gstengine::initPlayer(QFrame *frame, QString filename)
{
    GstPad *teepad;
    GstPad *pad;
    GstElement *audiosinkqueue;
    GstElement *eq_audioconvert = NULL;
    GstElement *eq_audioconvert2 = NULL;
    GstElement *audiobin;
    GstElement *audiotee;
    GstElement *preamp;

    videoFrame = frame;

    playbin = gst_element_factory_make ("playbin2", "playbin");

    if (!createAudioSink())
        return false;

    if (!createVolumeCtrl())
        return false;

    audiobin = gst_bin_new ("audiobin");
    audiotee = gst_element_factory_make ("tee", "audiotee");

    audiosinkqueue = gst_element_factory_make ("queue", "audiosinkqueue");

    equalizer = gst_element_factory_make ("equalizer-10bands", "equalizer");
    eq_audioconvert = gst_element_factory_make ("audioconvert", "audioconvert");
    eq_audioconvert2 = gst_element_factory_make ("audioconvert", "audioconvert2");
    preamp = gst_element_factory_make ("volume", "preamp");

    gst_bin_add_many (GST_BIN (audiobin), audiotee, volumeCtrl, audiosinkqueue, audioSink, NULL);
    gst_bin_add_many (GST_BIN (audiobin), eq_audioconvert, eq_audioconvert2, equalizer, preamp, NULL);

    // Ghost pad the audio bin so audio is passed from the bin into the tee
    teepad = gst_element_get_pad (audiotee, "sink");
    gst_element_add_pad (audiobin, gst_ghost_pad_new ("sink", teepad));
    gst_object_unref (teepad);

    gst_element_link_many (audiosinkqueue, eq_audioconvert, preamp,
                           equalizer, eq_audioconvert2, volumeCtrl, audioSink, NULL);

    g_object_set (G_OBJECT (playbin), "audio-sink", audiobin, NULL);

    // Link the first tee pad to the primary audio sink queue
    GstPad *sinkpad = gst_element_get_pad (audiosinkqueue, "sink");
    pad = gst_element_get_request_pad (audiotee, "src%d");
    g_object_set(audiotee, "alloc-pad", pad, NULL);
    gst_pad_link (pad, sinkpad);
    gst_object_unref (GST_OBJECT (pad));

    bus = gst_pipeline_get_bus (GST_PIPELINE (playbin));
    gst_bus_add_watch (bus, busCallback, NULL);
    gst_object_unref (bus);

    g_object_set (G_OBJECT (playbin), "uri", QString("file://"+filename).toLocal8Bit().constData(), NULL);

    fileType(filename);

    return true;
}

void gstengine::fileType(QString filename)
{
        GstElement *filesrc, *typefind, *fakesink;

        pipeline = gst_pipeline_new ("pipe");

        filesrc = gst_element_factory_make ("filesrc", "source");
        g_object_set (G_OBJECT (filesrc), "location", filename.toLocal8Bit().constData(), NULL);
        typefind = gst_element_factory_make ("typefind", "typefinder");
        g_signal_connect (typefind, "have-type", G_CALLBACK (findType), NULL);
        fakesink = gst_element_factory_make ("fakesink", "sink");

        gst_bin_add_many (GST_BIN (pipeline), filesrc, typefind, fakesink, NULL);
        gst_element_link_many (filesrc, typefind, fakesink, NULL);
        gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING);

        gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL);
        gst_object_unref (GST_OBJECT (pipeline));
        pipeline = 0;
}

bool gstengine::isVideo(){
    return thisVideo;
}

bool gstengine::playFile(){

    if(thisVideo) {

        if (widgetVideo != 0) {
            widgetVideo->~videoWidget();
            widgetVideo = 0;
        }

        GstElement *videoBin = gst_bin_new ("videobin");

        videoSink = gst_element_factory_make ("xvimagesink", "xvideoout");
        if (videoSink == NULL)
            return false;

        textoverlay = gst_element_factory_make ("textoverlay", "text");
        if (textoverlay == NULL)
            return false;

        g_object_set(G_OBJECT(textoverlay),"halign", "left","valign", "top",NULL);
        if (GST_VERSION_MAJOR < 32)
            g_object_set(G_OBJECT(textoverlay),"color", 4000180000 ,NULL);
        g_object_set(G_OBJECT(textoverlay),"font-desc", "FreeSerif, Bold 30",NULL);

        xinfo = new QX11Info();
        display = xinfo->display();
        g_object_set(G_OBJECT (videoSink), "display", display, NULL);
        g_object_set(G_OBJECT (videoSink), "force-aspect-ratio", true, NULL);

        widgetVideo = new videoWidget(videoFrame);
        connect(widgetVideo,SIGNAL(clickVideoWidget()), this, SLOT(clickVideoWidget()));

        gst_bin_add_many (GST_BIN (videoBin), textoverlay, videoSink, NULL);
        gst_element_link_many (textoverlay, videoSink, NULL);

        GstPad *pad = gst_element_get_static_pad (textoverlay, "video_sink");
        gst_element_add_pad ( videoBin , gst_ghost_pad_new ("sink", pad));
        gst_object_unref (GST_OBJECT (pad));

        g_object_set(G_OBJECT (playbin), "video-sink", videoBin, NULL);
        //gst_element_set_state (GST_ELEMENT (videoBin), GST_STATE_READY);
        setGeometryWV(currGeometry);
        initVideo();
    }
    gst_element_set_state (playbin, GST_STATE_PLAYING);
    initEngine = true;
    return true;
}

void gstengine::stopPlayer() {
    if (playbin) {
        gst_element_set_state (playbin, GST_STATE_NULL);
        gst_object_unref (GST_OBJECT (playbin));
        playbin = 0;
        initEngine = false;
    }
}

void gstengine::pausePlayer()
{
    if (isPlayerPlayed()) {
        gst_element_set_state (playbin, GST_STATE_PAUSED);
    } else {
        gst_element_set_state (playbin, GST_STATE_PLAYING);
    }
}

bool gstengine::isPlayerPlayed(){
    GstState state;
    gst_element_get_state( GST_ELEMENT(playbin), &state, NULL, GST_SECOND);
    if (state == GST_STATE_PLAYING) {
        return true;
    }
    return false;
}

void gstengine::seekPlayer(gint64 time)
{
    if (!playbin)
        return;
    if (!gst_element_seek_simple(playbin, GST_FORMAT_TIME,
                          (GstSeekFlags) (GST_SEEK_FLAG_FLUSH | GST_SEEK_FLAG_KEY_UNIT), time * GST_SECOND))
        return;
}

int gstengine::getPlayerDuration()
{
    static gint64 fulltime;
    GstFormat fmt = GST_FORMAT_TIME;
    gst_element_query_duration(playbin, &fmt, &fulltime);
    return fulltime / GST_SECOND;
}

qint64 gstengine::getPlayerPosition()
{    
    qint64 time;
    GstFormat fmt = GST_FORMAT_TIME;
    gst_element_query_position(playbin, &fmt, &time);
    return time;
}

bool gstengine::initDVB(QFrame *frame, QStringList paramList)
{
    GstElement *demuxer, *vqueue, *aqueue;
    GstElement *vdecoder, *adecoder;
    GstPad *videopad, *audiopad;

    videoFrame = frame;

    pipeline = gst_pipeline_new ("pipe");

    equalizer = gst_element_factory_make ("equalizer-10bands", "equalizer");

    dvbsrc = gst_element_factory_make ("dvbsrc", NULL);

    typeCh = paramList.at(0);

    g_object_set (dvbsrc, "modulation",   paramList.at(1).toInt(), NULL);
    g_object_set (dvbsrc, "trans-mode",   paramList.at(2).toInt(), NULL);
    g_object_set (dvbsrc, "bandwidth",    paramList.at(3).toInt(), NULL);
    g_object_set (dvbsrc, "frequency",    paramList.at(4).toUInt(), NULL);
    g_object_set (dvbsrc, "code-rate-lp", paramList.at(5).toInt(), NULL);
    g_object_set (dvbsrc, "code-rate-hp", paramList.at(6).toInt(), NULL);
    g_object_set (dvbsrc, "guard",        paramList.at(7).toInt(), NULL);
    g_object_set (dvbsrc, "hierarchy",    paramList.at(8).toInt(), NULL);
    g_object_set (dvbsrc, "pids",         paramList.at(9).toLocal8Bit().data(), NULL);

    demuxer = gst_element_factory_make ("mpegtsdemux", "demux");
    g_signal_connect (demuxer, "pad-added", G_CALLBACK (on_pad_added), NULL);
    gst_bin_add_many (GST_BIN (pipeline), dvbsrc, demuxer, NULL);
    gst_element_link_many (dvbsrc, demuxer, NULL);

    if (typeCh == "TV") {
        //video-bin
        d_video = gst_bin_new ("videobin");
        vqueue = make_queue();
        vdecoder = gst_element_factory_make ("ffdec_h264", "decoder");

        if (!createVideoSink())
            return false;

        //connect
        gst_bin_add_many (GST_BIN (d_video), vqueue, vdecoder, textoverlay, videoSink, NULL);
        gst_element_link_many (vqueue, vdecoder, textoverlay, videoSink, NULL);

        //videopad
        videopad = gst_element_get_static_pad (vqueue, "sink");
        gst_element_add_pad (d_video, gst_ghost_pad_new ("sink", videopad));
        gst_object_unref (videopad);

        gst_bin_add (GST_BIN (pipeline), d_video);
    }

    if (!createAudioSink())
        return false;

    adecoder = gst_element_factory_make ("mad", "adec");

    if (!createVolumeCtrl())
        return false;

    GstElement *eq_audioconvert = NULL;
    GstElement *eq_audioconvert2 = NULL;
    GstElement *preamp;

    eq_audioconvert = gst_element_factory_make ("audioconvert", "audioconvert");
    eq_audioconvert2 = gst_element_factory_make ("audioconvert", "audioconvert2");
    preamp = gst_element_factory_make ("volume", "preamp");

    aqueue = make_queue();
    d_audio = gst_bin_new ("audiobin");
    gst_bin_add_many (GST_BIN (d_audio), aqueue, adecoder, eq_audioconvert, preamp,
                      equalizer, eq_audioconvert2, volumeCtrl, audioSink, NULL);
    gst_element_link_many(aqueue, adecoder, eq_audioconvert, preamp,
                          equalizer, eq_audioconvert2, volumeCtrl, audioSink, NULL);

    audiopad = gst_element_get_static_pad (aqueue, "sink");
    gst_element_add_pad (d_audio, gst_ghost_pad_new ("sink", audiopad));
    gst_object_unref (audiopad);
    gst_bin_add (GST_BIN (pipeline), d_audio);
    if (typeCh == "TV") {
        gst_element_set_state (GST_ELEMENT (videoSink), GST_STATE_READY);
        initVideo();
    }
    gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING);
    initEngine = true;
    return true;
}

bool gstengine::getStatePipeline()
{
    GstState state;
    if (pipeline) {
        gst_element_get_state(GST_ELEMENT (pipeline), &state, NULL, GST_SECOND );
        if ( state == GST_STATE_PLAYING || state == GST_STATE_PAUSED )
            return true;
    }
    return false;
}

void gstengine::stopPipeline()
{
    if (pipeline) {
        gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_NULL);
        gst_object_unref (pipeline);
        pipeline = 0;
        initEngine = false;
    }
}

bool gstengine::analogTv(QFrame *frame)
{
    videoFrame = frame;
    if (tvDevName.isEmpty())
        return false;

    pipeline = gst_pipeline_new ("analogtv");
    source = gst_element_factory_make ("v4l2src", NULL);
    if (source == NULL)
        return false;

    g_object_set (source, "device", (const char*)tvDevName.toLatin1(), NULL);

    if (!createVideoSink())
        return false;

    gst_bin_add_many (GST_BIN (pipeline), source, textoverlay, videoSink, NULL);
    gst_element_link_many (source, textoverlay, videoSink, NULL);
    gst_element_set_state (GST_ELEMENT (videoSink), GST_STATE_READY);
    initVideo();
    gst_element_set_state (GST_ELEMENT (pipeline), GST_STATE_PLAYING);    

    initEngine = true;

    return true;
}

void gstengine::setanalogTvCh(double freq)
{
    tuner = GST_TUNER( (GstTuner *) source);
    tunerCh =  gst_tuner_get_channel(tuner);
    gst_tuner_set_frequency(tuner, tunerCh, freq);
}

int gstengine::getStrenght()
{
    if (tuner != 0)
     return gst_tuner_signal_strength(tuner,tunerCh);
}

void gstengine::setFreq(double *band)
{
        g_object_set (G_OBJECT (equalizer),
                                  "band0", band[0],
                                  "band1", band[1],
                                  "band2", band[2],
                                  "band3", band[3],
                                  "band4", band[4],
                                  "band5", band[5],
                                  "band6", band[6],
                                  "band7", band[7],
                                  "band8", band[8],
                                  "band9", band[9],
                                  NULL);
}

void gstengine::setVolume(int volume)
{
    if(initEngine) {
        g_object_set ( G_OBJECT (volumeCtrl), "volume", (double)volume / 100, NULL);
        currVolume = volume;
    }
}

int gstengine::getVolume()
{
    return currVolume;
}

void gstengine::mute(bool mutefl)
{
    if(initEngine)
        g_object_set (G_OBJECT (volumeCtrl), "mute", mutefl , NULL);
}

getGstDevices::getGstDevices(QObject *parent) :
    QThread(parent)
{
    error = false;
    gst_init (0, 0);
}

void getGstDevices::run()
{
    deviceList.clear();
    getDevice("v4l2src",0);
    getDevice("alsasrc",1);
    getDevice("alsasink",2);
    this->exit();
}

void getGstDevices::getDevice(QString factory,int type)
{
    GstElementFactory* srcfactory = gst_element_factory_find(factory.toAscii());
    if (!srcfactory) {
        ermessage = "Cannot find "+factory+" device";
        //error = true;
        return;
    }
    GstElement* videosrc = gst_element_factory_create (srcfactory, "src");
    if (!videosrc) {
        ermessage = "Cannot create" + factory;
        error = true;
        return;
    }
    GstPropertyProbe* probe = GST_PROPERTY_PROBE(videosrc);
    const GParamSpec* pspec = gst_property_probe_get_property (probe, "device");
    GValueArray* array = gst_property_probe_probe_and_get_values (probe, pspec);
    if(!array){
        ermessage = "Not opened devices " + factory;
        //error = true;
        return;
    }
    for (gint n = 0; n < array->n_values; n++) {
        GValue *device;
        gchar *path; gchar *name;
        device = g_value_array_get_nth (array, n);
        g_object_set_property (G_OBJECT (videosrc), "device", device);
        if (gst_element_set_state (videosrc, GST_STATE_READY) != GST_STATE_CHANGE_SUCCESS) {
            continue;
        }
        g_object_get (G_OBJECT (videosrc), "device", &path, NULL);
        if (path == NULL)
            path = "Unknown";
        g_object_get (G_OBJECT (videosrc), "device-name", &name, NULL);
        if (name == NULL)
            name = "Unknown";
        gst_element_set_state (videosrc, GST_STATE_NULL);
        devInfo dev;
        dev.type = type;
        dev.name = name;
        dev.path = path;
        deviceList.append(dev);
    }
    gst_element_set_state (videosrc, GST_STATE_NULL);
    gst_object_unref(GST_OBJECT (videosrc));
}
